package com.zwjlg.baselibrary;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

import dalvik.system.DexClassLoader;

import static android.content.Context.MODE_PRIVATE;

/**
 * Created by LiGang on 2017/5/30.
 */

public class HotFix {
    private Context context;
    private static HotFix instance;

    private HotFix() {

    }

    public static HotFix getInstance() {
        if (instance == null) {
            synchronized (HotFix.class) {
                if (instance == null) {
                    instance = new HotFix();
                }
            }
        }
        return instance;
    }

    public void init(Context context) {
        this.context = context;
    }

    /**
     * 注入assets中的jar或dex或apk
     *
     * @param assetsPath
     */
    public void injectAssets(String assetsPath) {
        try {
            File dir = context.getDir("dexs", MODE_PRIVATE);
            File jarFile = new File(dir, assetsPath);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            InputStream in = context.getAssets().open(assetsPath);
            OutputStream out = new FileOutputStream(jarFile);
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
            inject(jarFile.getAbsolutePath());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * inject SD卡中的补丁
     *
     * @param sdcardPath
     */
    public void injectSdcardFile(String sdcardPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(sdcardPath);
            File dstFile = new File(context.getFilesDir(), "patch.dex");
            fos = new FileOutputStream(dstFile);
            byte[] buff = new byte[1024];
            int len;
            while ((len = fis.read(buff)) != -1) {
                fos.write(buff, 0, len);
            }
            inject(dstFile.getAbsolutePath());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * 注入assets中的jar或dex或apk
     *
     * @param assetsPath
     */
    public void injectHack(String assetsPath) {
        try {
            File hackDir = context.getDir("hackDir", MODE_PRIVATE);
            String fileName = assetsPath;
            if (assetsPath.contains("/")) {
                fileName = assetsPath.substring(assetsPath.lastIndexOf("/") + 1);
            }
            File hackJar = new File(hackDir, fileName);
            InputStream in = context.getAssets().open(assetsPath);
            OutputStream out = new FileOutputStream(hackJar);
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
            inject(hackJar.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 要注入的dex的路径
     *
     * @param path
     */
    public void inject(String path) {
        System.out.println("要注入的dex的路径>>>>" + path);
        try {
            // 获取classes的dexElements
            Class<?> cl = Class.forName("dalvik.system.BaseDexClassLoader");
            Object pathList = getField(cl, "pathList", context.getClassLoader());
            Object baseElements = getField(pathList.getClass(), "dexElements", pathList);
            // 获取patch_dex的dexElements（需要先加载dex）
            String dexopt = context.getDir("dexopt", 0).getAbsolutePath();
            DexClassLoader dexClassLoader = new DexClassLoader(path, dexopt, dexopt, context.getClassLoader());
            Object obj = getField(cl, "pathList", dexClassLoader);
            Object dexElements = getField(obj.getClass(), "dexElements", obj);

            // 合并两个Elements
            Object combineElements = combineArray(dexElements, baseElements);

            // 将合并后的Element数组重新赋值给app的classLoader
            setField(pathList.getClass(), "dexElements", pathList, combineElements);

            //======== 以下是测试是否成功注入 =================
            Object object = getField(pathList.getClass(), "dexElements", pathList);
            int length = Array.getLength(object);
            Log.e("BugFixApplication", "length = " + length);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过反射获取对象的属性值
     */
    private Object getField(Class<?> cl, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException {
        Field field = cl.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(object);
    }

    /**
     * 通过反射设置对象的属性值
     */
    private void setField(Class<?> cl, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = cl.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, value);
    }

    /**
     * 通过反射合并两个数组
     */
    private Object combineArray(Object firstArr, Object secondArr) {
        int firstLength = Array.getLength(firstArr);
        int secondLength = Array.getLength(secondArr);
        int length = firstLength + secondLength;

        Class<?> componentType = firstArr.getClass().getComponentType();
        Object newArr = Array.newInstance(componentType, length);
        for (int i = 0; i < length; i++) {
            if (i < firstLength) {
                Array.set(newArr, i, Array.get(firstArr, i));
            } else {
                Array.set(newArr, i, Array.get(secondArr, i - firstLength));
            }
        }
        return newArr;
    }
}
